<!DOCTYPE html><html class="theme-next gemini use-motion" lang="zh-CN"><head><meta name="generator" content="Hexo 3.9.0"><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=2"><meta name="theme-color" content="#222"><link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2"><link rel="stylesheet" href="/css/main.css?v=7.1.1"><link rel="apple-touch-icon" sizes="180x180" href="/images/my-apple-touch-icon-next.png?v=7.1.1"><link rel="icon" type="image/png" sizes="32x32" href="/images/my-32x32-next.png?v=7.1.1"><link rel="icon" type="image/png" sizes="16x16" href="/images/my-16x16-next.png?v=7.1.1"><link rel="mask-icon" href="/images/my-logo.svg?v=7.1.1" color="#222"><script id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '7.1.1',
    sidebar: {"position":"left","display":"post","offset":12,"onmobile":false,"dimmer":false},
    back2top: true,
    back2top_sidebar: false,
    fancybox: false,
    fastclick: false,
    lazyload: false,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script><meta name="description" content="Tinyhttpd是一个使用C语言编写的超轻量级的http服务器软件，本文对 Tinyhttpd 源码进行了剖析，有助于理解 web 服务器的原理"><meta name="keywords" content="Tinyhttpd,web服务器,http,源码解析"><meta property="og:type" content="article"><meta property="og:title" content="Tinyhttpd源码解析"><meta property="og:url" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode.html"><meta property="og:site_name" content="dhd的博客"><meta property="og:description" content="Tinyhttpd是一个使用C语言编写的超轻量级的http服务器软件，本文对 Tinyhttpd 源码进行了剖析，有助于理解 web 服务器的原理"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode/1_1.png"><meta property="og:image" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode/1_2.png"><meta property="og:image" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode/1_3.png"><meta property="og:image" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode/4_1.png"><meta property="og:updated_time" content="2019-11-18T07:46:11.985Z"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Tinyhttpd源码解析"><meta name="twitter:description" content="Tinyhttpd是一个使用C语言编写的超轻量级的http服务器软件，本文对 Tinyhttpd 源码进行了剖析，有助于理解 web 服务器的原理"><meta name="twitter:image" content="https://renshengjihe.cn/190528-TinyHttpdSourceCode/1_1.png"><link rel="canonical" href="https://renshengjihe.cn/190528-TinyHttpdSourceCode"><script id="page.configurations">
  CONFIG.page = {
    sidebar: "",
  };
</script><title>Tinyhttpd源码解析 | dhd的博客</title><noscript><style>
  .use-motion .motion-element,
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-title { opacity: initial; }

  .use-motion .logo,
  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style></noscript></head><body itemscope itemtype="http://schema.org/WebPage" lang="zh-CN"><div class="container sidebar-position-left page-post-detail"><div class="headband"></div><header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="header-inner"><div class="site-brand-wrapper"><div class="site-meta"><div class="custom-logo-site-title"><a href="/" class="brand" rel="start"><span class="logo-line-before"><i></i></span> <span class="site-title">dhd的博客</span><span class="logo-line-after"><i></i></span></a></div><h1 class="site-subtitle" itemprop="description">对酒当歌，人生几何</h1></div><div class="site-nav-toggle"> <button aria-label="切换导航栏"><span class="btn-bar"></span><span class="btn-bar"></span><span class="btn-bar"></span></button></div></div><nav class="site-nav"><ul id="menu" class="menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="menu-item-icon fa fa-fw fa-home"></i><br>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="menu-item-icon fa fa-fw fa-tags"></i><br>标签</a></li><li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="menu-item-icon fa fa-fw fa-th"></i><br>分类</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="menu-item-icon fa fa-fw fa-archive"></i><br>归档</a></li></ul></nav></div></header><main id="main" class="main"><div class="main-inner"><div class="content-wrap"><div id="content" class="content"><div id="posts" class="posts-expand"><article class="post post-type-normal" itemscope itemtype="http://schema.org/Article"><div class="post-block"><link itemprop="mainEntityOfPage" href="https://renshengjihe.cn/190528-TinyHttpdSourceCode.html"><span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="dhd"><meta itemprop="description" content="个人博客,分享一些C/C++ Linux 相关的学习笔记"><meta itemprop="image" content="/images/avatar.gif"></span><span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="dhd的博客"></span><header class="post-header"><h2 class="post-title" itemprop="name headline">Tinyhttpd源码解析</h2><div class="post-meta"><span class="post-time"><span class="post-meta-item-icon"><i class="fa fa-calendar-o"></i></span> <span class="post-meta-item-text">发表于</span> <time title="创建时间：2019-05-28 19:48:26" itemprop="dateCreated datePublished" datetime="2019-05-28T19:48:26+08:00">2019-05-28</time> <span class="post-meta-divider">|</span><span class="post-meta-item-icon"><i class="fa fa-calendar-check-o"></i></span> <span class="post-meta-item-text">更新于</span> <time title="修改时间：2019-11-18 15:46:11" itemprop="dateModified" datetime="2019-11-18T15:46:11+08:00">2019-11-18</time></span> <span class="post-category"><span class="post-meta-divider">|</span><span class="post-meta-item-icon"><i class="fa fa-folder-o"></i></span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/源码解析/" itemprop="url" rel="index"><span itemprop="name">源码解析</span></a></span></span> <span class="post-comments-count"><span class="post-meta-divider">|</span><span class="post-meta-item-icon"><i class="fa fa-comment-o"></i></span> <span class="post-meta-item-text">评论数：</span><a href="/190528-TinyHttpdSourceCode.html#comments" itemprop="discussionUrl"><span class="post-comments-count valine-comment-count" data-xid="/190528-TinyHttpdSourceCode.html" itemprop="commentCount"></span></a></span> <span id="/190528-TinyHttpdSourceCode.html" class="leancloud_visitors" data-flag-title="Tinyhttpd源码解析"><span class="post-meta-divider">|</span><span class="post-meta-item-icon"><i class="fa fa-eye"></i></span> <span class="post-meta-item-text">阅读次数：</span><span class="leancloud-visitors-count"></span></span><div class="post-description">Tinyhttpd是一个使用C语言编写的超轻量级的http服务器软件，本文对 Tinyhttpd 源码进行了剖析，有助于理解 web 服务器的原理</div></div></header><div class="post-body" itemprop="articleBody"><p>Tinyhttpd是一个超轻量级的http服务器软件，使用C语言编写，代码仅有500行左右，实际生产环境中并不实用，但通过研究这短小精悍的500行代码，有助于我们了解web服务器的本质，弄清楚在访问一个网页的过程中，服务器都做了哪些工作。</p><a id="more"></a><h2 id="源码下载＆编译"><a href="#源码下载＆编译" class="headerlink" title="源码下载＆编译"></a>源码下载＆编译</h2><p>TinyHttpd的原版代码下载</p><blockquote><p><a href="https://sourceforge.net/projects/tinyhttpd/" target="_blank" rel="noopener">https://sourceforge.net/projects/tinyhttpd/</a></p></blockquote><p>下载完成，解压后会得到这样一些文件：</p><p><img src="/190528-TinyHttpdSourceCode/1_1.png" alt="目录结构"></p><p>主要需要看的是 httpd.c 里边的源码。<br>在这之前，先<code>make</code>把代码编译一下，看一下执行后的效果。</p><p>编译的时候会报错，主要是一些线程函数和 Makefile 里的连接库错误，下面给出我的解决方案</p><ol><li>把 Makefile 文件中的编译选项中的 -lsocket 去掉。</li><li>436行<code>namelen</code>和491行<code>client_name_len</code>的变量类型改为 <code>socklen_t</code></li><li><p>accept_request用作线程的处理函数，函数原型需做出改变。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">void *accept_request(void *);</span><br><span class="line">void *accept_request(void * arg)</span><br><span class="line">&#123;</span><br><span class="line">	// 此处为了尽可能少的修改原有代码，参数名改为arg</span><br><span class="line">    int client = *(int*)arg;</span><br><span class="line">    ...</span><br><span class="line">    return NULL;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>496行<code>client_sock</code>参数类型错误，改为<code>&amp;client_sock</code></p></li><li><p>（可选）改端口号，TinyHttpd 默认的是动态绑定端口号，为方便后面使用改为固定 8080</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">u_short port = 8080;	// main函数，480行</span><br></pre></td></tr></table></figure></li><li><p>（可选）改IP地址</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// startup 函数	433行</span><br><span class="line">name.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);</span><br></pre></td></tr></table></figure></li></ol><p>至此，程序应该能正常编译通过，下面是示例程序的运行效果，</p><p>浏览器输入<code>http://127.0.0.1:8080/index.html</code>（ip和端口号要以自己的实际情况为准）<br><img src="/190528-TinyHttpdSourceCode/1_2.png" alt="示例1"></p><p><img src="/190528-TinyHttpdSourceCode/1_3.png" alt="示例1"></p><blockquote><p>ps: 此处如果第二个页面显示不出来，主要是由于缺少或找不到执行 perl 脚本的程序导致，安装 perl 并将 <code>htdocs/color.cgi</code> 的第一行改为自己电脑上的 perl 所在位置即可。</p></blockquote><h2 id="Tinyhttpd-运行流程"><a href="#Tinyhttpd-运行流程" class="headerlink" title="Tinyhttpd 运行流程"></a>Tinyhttpd 运行流程</h2><ol><li>main函数，调用 startup 创建监听套接字开始监听，循环接收客户端连接。</li><li>接收到一个客户端的连接后，创建一个新的线程，进入 accept_request 函数与客户端进行交互。</li><li>根据请求的类型，判断后续处理的方式，如果是 POST 请求或带参数的 GET 请求，则采取 CGI 的处理方式（execute_cgi 函数），否则，采取普通网页的处理方式（serve_file函数）。</li><li>数据传输完毕，关闭连接，线程退出。</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> server_sock = <span class="number">-1</span>;</span><br><span class="line">    u_short port = <span class="number">8080</span>;</span><br><span class="line">    <span class="keyword">int</span> client_sock = <span class="number">-1</span>;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">client_name</span>;</span></span><br><span class="line">    <span class="keyword">socklen_t</span> client_name_len = <span class="keyword">sizeof</span>(client_name);</span><br><span class="line">    <span class="keyword">pthread_t</span> newthread;</span><br><span class="line"></span><br><span class="line">    server_sock = startup(&amp;port);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"httpd running on port %d\n"</span>, port);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (<span class="number">1</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        client_sock = accept(server_sock,</span><br><span class="line">        	(struct sockaddr *)&amp;client_name,</span><br><span class="line">            &amp;client_name_len);</span><br><span class="line">            </span><br><span class="line">        <span class="keyword">if</span> (client_sock == <span class="number">-1</span>)</span><br><span class="line">        error_die(<span class="string">"accept"</span>);</span><br><span class="line">        <span class="comment">// 开启新线程</span></span><br><span class="line">        <span class="keyword">if</span> (pthread_create(&amp;newthread , <span class="literal">NULL</span>, </span><br><span class="line">        	accept_request, &amp;client_sock) != <span class="number">0</span>)</span><br><span class="line">        	perror(<span class="string">"pthread_create"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    close(server_sock);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">accept_request</span><span class="params">(<span class="keyword">void</span>* arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> client = *(<span class="keyword">int</span>*)arg;</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">1024</span>];</span><br><span class="line">    <span class="keyword">int</span> numchars;</span><br><span class="line">    <span class="keyword">char</span> method[<span class="number">255</span>];</span><br><span class="line">    <span class="keyword">char</span> url[<span class="number">255</span>];</span><br><span class="line">    <span class="keyword">char</span> path[<span class="number">512</span>];</span><br><span class="line">    <span class="keyword">size_t</span> i, j;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">stat</span> <span class="title">st</span>;</span></span><br><span class="line">    <span class="keyword">int</span> cgi = <span class="number">0</span>;	<span class="comment">// 是否cgi程序</span></span><br><span class="line">    <span class="keyword">char</span> *query_string = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">    i = <span class="number">0</span>; j = <span class="number">0</span>;</span><br><span class="line">	<span class="keyword">while</span> (!ISspace(buf[j]) &amp;&amp; (i &lt; <span class="keyword">sizeof</span>(method) - <span class="number">1</span>))</span><br><span class="line">    &#123;</span><br><span class="line">        method[i] = buf[j];</span><br><span class="line">        i++; j++;</span><br><span class="line">    &#125;</span><br><span class="line">    method[i] = <span class="string">'\0'</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (strcasecmp(method, <span class="string">"GET"</span>) &amp;&amp; strcasecmp(method, <span class="string">"POST"</span>))</span><br><span class="line">    &#123;</span><br><span class="line">        unimplemented(client);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (strcasecmp(method, <span class="string">"POST"</span>) == <span class="number">0</span>)</span><br><span class="line">        cgi = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (ISspace(buf[j]) &amp;&amp; (j &lt; <span class="keyword">sizeof</span>(buf)))</span><br><span class="line">    	j++;</span><br><span class="line">    <span class="keyword">while</span> (!ISspace(buf[j]) &amp;&amp; (i &lt; <span class="keyword">sizeof</span>(url) - <span class="number">1</span>) &amp;&amp; (j &lt; <span class="keyword">sizeof</span>(buf)))</span><br><span class="line">    &#123;</span><br><span class="line">        url[i] = buf[j];</span><br><span class="line">        i++; j++;</span><br><span class="line">    &#125;</span><br><span class="line">    url[i] = <span class="string">'\0'</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (strcasecmp(method, <span class="string">"GET"</span>) == <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        query_string = url;</span><br><span class="line">        <span class="keyword">while</span> ((*query_string != <span class="string">'?'</span>) &amp;&amp; (*query_string != <span class="string">'\0'</span>))</span><br><span class="line">        	query_string++;</span><br><span class="line">        <span class="keyword">if</span> (*query_string == <span class="string">'?'</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            cgi = <span class="number">1</span>;</span><br><span class="line">            *query_string = <span class="string">'\0'</span>;</span><br><span class="line">            query_string++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">sprintf</span>(path, <span class="string">"htdocs%s"</span>, url);</span><br><span class="line">    <span class="keyword">if</span> (path[<span class="built_in">strlen</span>(path) - <span class="number">1</span>] == <span class="string">'/'</span>)</span><br><span class="line">    	<span class="built_in">strcat</span>(path, <span class="string">"index.html"</span>);</span><br><span class="line">    <span class="keyword">if</span> (stat(path, &amp;st) == <span class="number">-1</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">while</span> ((numchars &gt; <span class="number">0</span>) &amp;&amp; <span class="built_in">strcmp</span>(<span class="string">"\n"</span>, buf))</span><br><span class="line">            numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">        not_found(client);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> ((st.st_mode &amp; S_IFMT) == S_IFDIR)</span><br><span class="line">        	<span class="built_in">strcat</span>(path, <span class="string">"/index.html"</span>);</span><br><span class="line">        <span class="keyword">if</span> ((st.st_mode &amp; S_IXUSR) ||</span><br><span class="line">        		(st.st_mode &amp; S_IXGRP) ||</span><br><span class="line">        		(st.st_mode &amp; S_IXOTH)    )</span><br><span class="line">        	cgi = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (!cgi)</span><br><span class="line">        	serve_file(client, path);</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        	execute_cgi(client, path, method, query_string);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    close(client);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="对-普通网页-的处理"><a href="#对-普通网页-的处理" class="headerlink" title="对 普通网页 的处理"></a>对 普通网页 的处理</h2><ul><li>对于普通网页，TinyHttpd 会尝试打开该文件；</li><li>如果打开成功，会将响应头和文件内容一并发给客户端进行响应；</li><li>如果打开失败，会将网页找不到的错误发给客户端，这就是 404 not fount 出现的原因。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">serve_file</span><span class="params">(<span class="keyword">int</span> client, <span class="keyword">const</span> <span class="keyword">char</span> *filename)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	FILE *resource = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">int</span> numchars = <span class="number">1</span>;</span><br><span class="line">	<span class="keyword">char</span> buf[<span class="number">1024</span>];</span><br><span class="line"></span><br><span class="line">	buf[<span class="number">0</span>] = <span class="string">'A'</span>; buf[<span class="number">1</span>] = <span class="string">'\0'</span>;</span><br><span class="line">	<span class="keyword">while</span> ((numchars &gt; <span class="number">0</span>) &amp;&amp; <span class="built_in">strcmp</span>(<span class="string">"\n"</span>, buf))  </span><br><span class="line">    	numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">	</span><br><span class="line">    resource = fopen(filename, <span class="string">"r"</span>);</span><br><span class="line">	<span class="keyword">if</span> (resource == <span class="literal">NULL</span>)</span><br><span class="line">    	not_found(client);	<span class="comment">// 404</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">    	headers(client, filename); <span class="comment">// 响应头</span></span><br><span class="line">    	cat(client, resource); <span class="comment">// 网页主体</span></span><br><span class="line">    &#125;</span><br><span class="line">    fclose(resource);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="对-CGI程序-的处理"><a href="#对-CGI程序-的处理" class="headerlink" title="对 CGI程序 的处理"></a>对 CGI程序 的处理</h2><ul><li>对于可执行的CGI程序，TinyHttpd 会 fork 一个进程进行执行。</li><li>通过两个管道（cgi_output、cgi_input）进行父子进程通信；</li><li>子进程（CGI程序）将将标准输入输出重定向至两个管道，这样的效果是：CGI程序中的标准输入会从 cgi_input 管道读取，标准输出会输出到 cgi_output 管道，所以在编写CGI程序时，只需要通过相应编程语言的输入输出函数进行操作，而不需要额外的接口。</li><li>父进程会将 cgi_output 管道的内容发送给到客户端，cgi_input 管道的内容发送给子进程。</li></ul><p><img src="/190528-TinyHttpdSourceCode/4_1.png" alt="CGI示意图"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">execute_cgi</span><span class="params">(<span class="keyword">int</span> client, <span class="keyword">const</span> <span class="keyword">char</span> *path,</span></span></span><br><span class="line"><span class="function"><span class="params">                 <span class="keyword">const</span> <span class="keyword">char</span> *method, <span class="keyword">const</span> <span class="keyword">char</span> *query_string)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">1024</span>];</span><br><span class="line">    <span class="keyword">int</span> cgi_output[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">int</span> cgi_input[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">pid_t</span> pid;</span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line">    <span class="keyword">int</span> i;</span><br><span class="line">    <span class="keyword">char</span> c;</span><br><span class="line">    <span class="keyword">int</span> numchars = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">int</span> content_length = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">    buf[<span class="number">0</span>] = <span class="string">'A'</span>; buf[<span class="number">1</span>] = <span class="string">'\0'</span>;</span><br><span class="line">    <span class="keyword">if</span> (strcasecmp(method, <span class="string">"GET"</span>) == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">while</span> ((numchars &gt; <span class="number">0</span>) &amp;&amp; <span class="built_in">strcmp</span>(<span class="string">"\n"</span>, buf)) </span><br><span class="line">            numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">    <span class="keyword">else</span>    <span class="comment">/* POST */</span></span><br><span class="line">    &#123;</span><br><span class="line">        numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">        <span class="keyword">while</span> ((numchars &gt; <span class="number">0</span>) &amp;&amp; <span class="built_in">strcmp</span>(<span class="string">"\n"</span>, buf))</span><br><span class="line">        &#123;</span><br><span class="line">            buf[<span class="number">15</span>] = <span class="string">'\0'</span>;</span><br><span class="line">            <span class="keyword">if</span> (strcasecmp(buf, <span class="string">"Content-Length:"</span>) == <span class="number">0</span>)</span><br><span class="line">            	content_length = atoi(&amp;(buf[<span class="number">16</span>]));</span><br><span class="line">            numchars = get_line(client, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (content_length == <span class="number">-1</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            bad_request(client);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">sprintf</span>(buf, <span class="string">"HTTP/1.0 200 OK\r\n"</span>);</span><br><span class="line">    send(client, buf, <span class="built_in">strlen</span>(buf), <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (pipe(cgi_output) &lt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        cannot_execute(client);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (pipe(cgi_input) &lt; <span class="number">0</span>) </span><br><span class="line">    &#123;</span><br><span class="line">        cannot_execute(client);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ( (pid = fork()) &lt; <span class="number">0</span> ) </span><br><span class="line">    &#123;</span><br><span class="line">        cannot_execute(client);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (pid == <span class="number">0</span>)  <span class="comment">/* child: CGI script */</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">char</span> meth_env[<span class="number">255</span>];</span><br><span class="line">        <span class="keyword">char</span> query_env[<span class="number">255</span>];</span><br><span class="line">        <span class="keyword">char</span> length_env[<span class="number">255</span>];</span><br><span class="line"></span><br><span class="line">        dup2(cgi_output[<span class="number">1</span>], <span class="number">1</span>);	<span class="comment">// 输出重定向</span></span><br><span class="line">        dup2(cgi_input[<span class="number">0</span>], <span class="number">0</span>);	<span class="comment">// 输入重定向</span></span><br><span class="line">        close(cgi_output[<span class="number">0</span>]);</span><br><span class="line">        close(cgi_input[<span class="number">1</span>]);</span><br><span class="line">        <span class="built_in">sprintf</span>(meth_env, <span class="string">"REQUEST_METHOD=%s"</span>, method);</span><br><span class="line">        putenv(meth_env);</span><br><span class="line">        <span class="keyword">if</span> (strcasecmp(method, <span class="string">"GET"</span>) == <span class="number">0</span>)</span><br><span class="line">        &#123;	<span class="comment">//GET方式，数据以字符串的形式存入QUERY_STRING环境变量</span></span><br><span class="line">            <span class="built_in">sprintf</span>(query_env, <span class="string">"QUERY_STRING=%s"</span>, query_string);</span><br><span class="line">            putenv(query_env);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;   <span class="comment">//POST方式，数据的长度以字符串的形式存入CONTENT_LENGTH环境变量</span></span><br><span class="line">        	<span class="comment">// 数据的内容通过 cgi_input 管道发送给CGI程序</span></span><br><span class="line">            <span class="built_in">sprintf</span>(length_env, <span class="string">"CONTENT_LENGTH=%d"</span>, content_length);</span><br><span class="line">            putenv(length_env);</span><br><span class="line">        &#125;</span><br><span class="line">        execl(path, path, <span class="literal">NULL</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">else</span> </span><br><span class="line">    &#123;    <span class="comment">/* parent */</span></span><br><span class="line">        close(cgi_output[<span class="number">1</span>]);</span><br><span class="line">        close(cgi_input[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">if</span> (strcasecmp(method, <span class="string">"POST"</span>) == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; content_length; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            recv(client, &amp;c, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line">            write(cgi_input[<span class="number">1</span>], &amp;c, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (read(cgi_output[<span class="number">0</span>], &amp;c, <span class="number">1</span>) &gt; <span class="number">0</span>)</span><br><span class="line">        	send(client, &amp;c, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        close(cgi_output[<span class="number">0</span>]);</span><br><span class="line">        close(cgi_input[<span class="number">1</span>]);</span><br><span class="line">        waitpid(pid, &amp;status, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><footer class="post-footer"><div class="post-tags"> <a href="/tags/源码解析/" rel="tag"># 源码解析</a></div><div class="post-nav"><div class="post-nav-next post-nav-item"><a href="/190527-vimCCppEnv.html" rel="next" title="vim打造C/C++开发环境"><i class="fa fa-chevron-left"></i> vim打造C/C++开发环境</a></div><span class="post-nav-divider"></span><div class="post-nav-prev post-nav-item"> <a href="/190613-CPointer.html" rel="prev" title="C指针总结">C指针总结<i class="fa fa-chevron-right"></i></a></div></div></footer></div></article></div></div><div class="comments" id="comments"></div></div><div class="sidebar-toggle"><div class="sidebar-toggle-line-wrap"><span class="sidebar-toggle-line sidebar-toggle-line-first"></span><span class="sidebar-toggle-line sidebar-toggle-line-middle"></span><span class="sidebar-toggle-line sidebar-toggle-line-last"></span></div></div><aside id="sidebar" class="sidebar"><div class="sidebar-inner"><ul class="sidebar-nav motion-element"><li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap"> 文章目录</li><li class="sidebar-nav-overview" data-target="site-overview-wrap"> 站点概览</li></ul><div class="site-overview-wrap sidebar-panel"><div class="site-overview"><div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person"><p class="site-author-name" itemprop="name">dhd</p><div class="site-description motion-element" itemprop="description">个人博客,分享一些C/C++ Linux 相关的学习笔记</div></div><nav class="site-state motion-element"><div class="site-state-item site-state-posts"> <a href="/archives/"><span class="site-state-item-count">16</span> <span class="site-state-item-name">日志</span></a></div><div class="site-state-item site-state-categories"> <a href="/categories/"><span class="site-state-item-count">7</span> <span class="site-state-item-name">分类</span></a></div><div class="site-state-item site-state-tags"> <a href="/tags/"><span class="site-state-item-count">8</span> <span class="site-state-item-name">标签</span></a></div></nav><div class="links-of-author motion-element"><span class="links-of-author-item"><a href="mailto:donghaodong555@foxmail.com" title="E-Mail &rarr; mailto:donghaodong555@foxmail.com" rel="noopener" target="_blank"><i class="fa fa-fw fa-envelope"></i></a></span></div></div></div><div class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active"><div class="post-toc"><div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#源码下载＆编译"><span class="nav-number">1.</span> <span class="nav-text">源码下载＆编译</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Tinyhttpd-运行流程"><span class="nav-number">2.</span> <span class="nav-text">Tinyhttpd 运行流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#对-普通网页-的处理"><span class="nav-number">3.</span> <span class="nav-text">对 普通网页 的处理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#对-CGI程序-的处理"><span class="nav-number">4.</span> <span class="nav-text">对 CGI程序 的处理</span></a></li></ol></div></div></div></div></aside></div></main><footer id="footer" class="footer"><div class="footer-inner"><div class="copyright">&copy; <span itemprop="copyrightYear">2019</span><span class="with-love" id="animate"><i class="fa fa-user"></i></span> <span class="author" itemprop="copyrightHolder">dhd</span></div></div></footer><div class="back-to-top"><i class="fa fa-arrow-up"></i></div></div><script>
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script><script color="0,0,255" opacity="0.5" zindex="-1" count="99" src="/lib/canvas-nest/canvas-nest.min.js"></script><script src="/lib/jquery/index.js?v=2.1.3"></script><script src="/lib/velocity/velocity.min.js?v=1.2.1"></script><script src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script><script src="/js/utils.js?v=7.1.1"></script><script src="/js/motion.js?v=7.1.1"></script><script src="/js/affix.js?v=7.1.1"></script><script src="/js/schemes/pisces.js?v=7.1.1"></script><script src="/js/scrollspy.js?v=7.1.1"></script><script src="/js/post-details.js?v=7.1.1"></script><script src="/js/next-boot.js?v=7.1.1"></script><script src="//cdn1.lncld.net/static/js/3.11.1/av-min.js"></script><script src="//unpkg.com/valine/dist/Valine.min.js"></script><script>
  var GUEST = ['nick', 'mail', 'link'];
  var guest = 'nick,mail,link';
  guest = guest.split(',').filter(function(item) {
    return GUEST.indexOf(item) > -1;
  });
  new Valine({
    el: '#comments',
    verify: false,
    notify: false,
    appId: 'f6cPnWydCEaioX6iq6AoSBPz-gzGzoHsz',
    appKey: 'TRF9THIRqNFdQRvrCURnmfBG',
    placeholder: 'Just go go',
    avatar: 'mm',
    meta: guest,
    pageSize: '10' || 10,
    visitor: false,
    lang: '' || 'zh-cn'
  });
</script><script>
    
    function addCount(Counter) {
      var $visitors = $('.leancloud_visitors');
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();

      Counter('get', '/classes/Counter', { where: JSON.stringify({ url }) })
        .done(function({ results }) {
          if (results.length > 0) {
            var counter = results[0];
            
            Counter('put', '/classes/Counter/' + counter.objectId, JSON.stringify({ time: { '__op': 'Increment', 'amount': 1 } }))
            
              .done(function() {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.time + 1);
              })
            
              .fail(function ({ responseJSON }) {
                console.log('Failed to save Visitor num, with error message: ' + responseJSON.error);
              })
          } else {
            
              Counter('post', '/classes/Counter', JSON.stringify({ title: title, url: url, time: 1 }))
                .done(function() {
                  var $element = $(document.getElementById(url));
                  $element.find('.leancloud-visitors-count').text(1);
                })
                .fail(function() {
                  console.log('Failed to create');
                });
            
          }
        })
        .fail(function ({ responseJSON }) {
          console.log('LeanCloud Counter Error: ' + responseJSON.code + ' ' + responseJSON.error);
        });
    }
    

    $(function() {
      $.get('https://app-router.leancloud.cn/2/route?appId=' + 'hrnLXd6od62r2eJJ9yYsWO74-gzGzoHsz')
        .done(function({ api_server }) {
          var Counter = function(method, url, data) {
            return $.ajax({
              method: method,
              url: 'https://' + api_server + '/1.1' + url,
              headers: {
                'X-LC-Id': 'hrnLXd6od62r2eJJ9yYsWO74-gzGzoHsz',
                'X-LC-Key': 'N3zDUUUGyqewKV2RLoozARF2',
                'Content-Type': 'application/json',
              },
              data: data
            });
          };
          
            addCount(Counter);
          
        });
    });
  </script><script>
    (function(){
      var bp = document.createElement('script');
      var curProtocol = window.location.protocol.split(':')[0];
      bp.src = (curProtocol === 'https') ? 'https://zz.bdstatic.com/linksubmit/push.js' : 'http://push.zhanzhang.baidu.com/push.js';
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(bp, s);
    })();
  </script><script>
  $('.highlight').not('.gist .highlight').each(function(i, e) {
    var $wrap = $('<div>').addClass('highlight-wrap');
    $(e).after($wrap);
    $wrap.append($('<button>').addClass('copy-btn').append('复制').on('click', function(e) {
      var code = $(this).parent().find('.code').find('.line').map(function(i, e) {
        return $(e).text();
      }).toArray().join('\n');
      var ta = document.createElement('textarea');
      var yPosition = window.pageYOffset || document.documentElement.scrollTop;
      ta.style.top = yPosition + 'px'; // Prevent page scroll
      ta.style.position = 'absolute';
      ta.style.opacity = '0';
      ta.readOnly = true;
      ta.value = code;
      document.body.appendChild(ta);
      ta.select();
      ta.setSelectionRange(0, code.length);
      ta.readOnly = false;
      var result = document.execCommand('copy');
      
        if (result) $(this).text('复制成功');
        else $(this).text('复制失败');
      
      ta.blur(); // For iOS
      $(this).blur();
    })).on('mouseleave', function(e) {
      var $b = $(this).find('.copy-btn');
      setTimeout(function() {
        $b.text('复制');
      }, 300);
    }).append(e);
  })
</script></body></html>